//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.TridentUtilities
{
    #region Using directives

    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Linq;
    using System.Reflection;
    using System.Windows;
    using Microsoft.Practices.EnterpriseLibrary.Logging;

    #endregion

    /// <summary>
    /// Represents a file and its references.
    /// </summary>
    [Serializable]
    public class FileReference : INotifyPropertyChanged
    {
        #region Private fields
        /// <summary>
        /// True if this file contains an Activity, False otherwise.
        /// </summary>
        private bool? containsActivity;

        /// <summary>
        /// True if a copy of this file exists in registry.
        /// </summary>
        private bool? copyExistsInRegistry;

        /// <summary>
        /// Full path of the file.
        /// </summary>
        private string filePath;

        /// <summary>
        /// Display name for the file.
        /// This is file name of the file contained in FilePath.
        /// </summary>
        private string displayName;

        /// <summary>
        /// Collection of file name of references.
        /// </summary>
        private Collection<string> references;

        /// <summary>
        /// Collection of file name of references.
        /// </summary>
        private ObservableCollection<string> displayReferences = new ObservableCollection<string>();

        /// <summary>
        /// True if references list box is visible, False otherwise.
        /// </summary>
        [field: NonSerialized]
        private Visibility referenceVisibility = Visibility.Collapsed;

        /// <summary>
        /// True if a existing file image needs to be updated. False otherwise.
        /// </summary>
        private bool updateExisting;

        /// <summary>
        /// The file image in the registry to update.
        /// </summary>
        private AssemblyMetadata assemblyInRegistryToUpdate; 
        #endregion

        #region Public properties
        /// <summary>
        /// Gets a value indicating whether this file contains an Activity or not.
        /// </summary>
        public bool ContainsActivity
        {
            get
            {
                if (!this.containsActivity.HasValue)
                {
                    bool isActivity = false;
                    string selectedFile = this.FilePath;
                    if (Helper.IsManagedAssembly(selectedFile))
                    {
                        try
                        {
                            // Find out if this file is an activity DLL or not.
                            Assembly assembly = AssemblyLoad.LoadFrom(selectedFile);
                            isActivity = assembly.GetTypes().Any(type => type.IsSubclassOf(typeof(System.Workflow.ComponentModel.Activity)));
                        }
                        catch (Exception)
                        {
                            // Just log the error. This file will be marked as non-activity.
                            Logger.Write("BuildReferences(): Could not load assembly - " + selectedFile);
                        }
                    }

                    this.containsActivity = isActivity;
                }

                return this.containsActivity.Value;
            }

            set
            {
                this.containsActivity = value;
            }
        }

        /// <summary>
        /// Gets or sets the value indicating whether a copy of the file exists in registry.
        /// True if the file exists in registry; False otherwise ; Null if the file exists and any of the referring files are new(only valid for activity Dlls).
        /// </summary>
        public bool? CopyExistsInRegistry
        {
            get
            {
                return this.copyExistsInRegistry;
            }
            set
            {
                this.copyExistsInRegistry = value;
            }
        }
        

        /// <summary>
        /// The Id of the registry copy of the file.
        /// </summary>
        public Guid AssemblyImageIdOfCopy { get; set; }

        /// <summary>
        /// Gets or sets the value indicating whether the user has selected to update an existing assembly.
        /// </summary>
        public bool UpdateExisting
        {
            get
            {
                return this.updateExisting;
            }
            set
            {
                if (this.updateExisting != value)
                {
                    this.updateExisting = value;
                    this.RaisePropertyChanged("ShowAssemblyToUpdate");
                }
            }
        }

        /// <summary>
        /// Gets the value indicating whether the border displaying the updated details should be shown or not.
        /// Needed as we cannot raise property changed event on the update existing property.
        /// </summary>
        public bool ShowAssemblyToUpdate
        {
            get
            {
                return this.updateExisting;
            }
        }

        /// <summary>
        /// Gets or sets the file path.
        /// </summary>
        public string FilePath
        {
            get
            {
                return this.filePath; 
            }

            set
            {
                this.filePath = value;
                this.DisplayName = Helper.GetFileName(value);
            }
        }

        /// <summary>
        /// Gets or sets the display name.
        /// </summary>
        public string DisplayName
        {
            get
            {
                return this.displayName;
            }
            set
            {
                this.displayName = value;
                this.RaisePropertyChanged("DisplayName");
            }
        }

        /// <summary>
        /// Gets or sets the references.
        /// </summary>
        public Collection<string> References
        {
            get
            {
                return this.references;
            }

            set
            {
                Collection<string> references = new Collection<string>();
                for (int index = 0; index < value.Count; index++)
                {
                    references.Add(value[index]);
                }

                this.references = references;
                this.displayReferences.Clear();
                foreach (string reference in references)
                {
                    displayReferences.Add(Helper.GetFileName(reference));
                }
                if (references.Count > 0)
                {
                    this.ReferenceVisibility = Visibility.Visible;
                }
                else
                {
                    this.ReferenceVisibility = Visibility.Collapsed;
                }
            }
        }

        /// <summary>
        /// Gets or sets the display references.
        /// </summary>
        public ObservableCollection<string> DisplayReferences
        {
            get
            {
                return this.displayReferences;
            }

            set
            {
                this.displayReferences.Clear();
                for (int index = 0; index < value.Count; index++)
                {
                    displayReferences.Add(Helper.GetFileName(value[index]));
                }
            }
        }

        /// <summary>
        /// Gets or sets the visibility of references.
        /// </summary>
        public Visibility ReferenceVisibility
        {
            get
            {
                return this.referenceVisibility;
            }
            set
            {
                this.referenceVisibility = value;
                this.RaisePropertyChanged("ReferenceVisibility");
            }
        }

        /// <summary>
        /// Gets or sets the assembly to update in the registry.
        /// </summary>
        public AssemblyMetadata AssemblyInRegistryToUpdate
        {
            get
            {
                return this.assemblyInRegistryToUpdate;
            }
            set
            {
                this.assemblyInRegistryToUpdate = value;
                this.RaisePropertyChanged("AssemblyInRegistryToUpdate");
            }
        }

        /// <summary>
        /// Gets or sets the file checksum.
        /// </summary>
        public string Checksum { get; set; }

        #endregion

        #region constructors
        /// <summary>
        /// Creates an instance of the file reference object.
        /// </summary>
        /// <param name="filePath">
        /// Full path of the file.
        /// </param>
        /// <param name="references">
        /// References of this file.
        /// </param>
        /// <param name="activityAssembly">
        /// True if this file contains an activity, False otherwise.
        /// </param>
        public FileReference(string filePath, Collection<string> references)
        {
            this.FilePath = filePath;
            this.References = references;
            this.containsActivity = null;
        }

        #endregion

        #region public methods
        /// <summary>
        /// Create a copy of the filereference object.
        /// </summary>
        /// <param name="fileReference">The source file.</param>
        /// <returns>The copy created.</returns>
        public static FileReference CreateCopy(FileReference fileReference)
        {
            FileReference newFileReference =
                new FileReference(fileReference.FilePath, fileReference.References);
            newFileReference.containsActivity = fileReference.ContainsActivity;

            if (fileReference.CopyExistsInRegistry.HasValue && (bool)fileReference.CopyExistsInRegistry)
            {
                newFileReference.CopyExistsInRegistry = true;
                newFileReference.AssemblyImageIdOfCopy = fileReference.AssemblyImageIdOfCopy;
            }
            if (fileReference.UpdateExisting)
            {
                newFileReference.UpdateExisting = true;
                newFileReference.AssemblyInRegistryToUpdate = new AssemblyMetadata()
                    {
                        Id = fileReference.AssemblyInRegistryToUpdate.Id,
                        ImportedBy = fileReference.AssemblyInRegistryToUpdate.ImportedBy,
                        ImportedTime = fileReference.AssemblyInRegistryToUpdate.ImportedTime,
                        Name = fileReference.AssemblyInRegistryToUpdate.Name,
                        DisplayName = fileReference.AssemblyInRegistryToUpdate.DisplayName
                    };
            }
            return newFileReference;
        }

        /// <summary>
        /// Retrieve the required filereference object from the filepath given.
        /// </summary>
        /// <param name="fileReferences">The collection of file references.</param>
        /// <param name="filePath">The file path.</param>
        /// <returns>The file reference which matches the filepath.</returns>
        public static FileReference GetFileReference(Collection<FileReference> fileReferences, string filePath)
        {
            #region Argument validations
            if (fileReferences == null)
            {
                throw new ArgumentNullException("fileReferences");
            }
            if (string.IsNullOrEmpty(filePath))
            {
                throw new ArgumentNullException("filePath");
            }
            #endregion

            return fileReferences.FirstOrDefault(f => string.Equals(f.FilePath, filePath, StringComparison.OrdinalIgnoreCase));
        }

        /// <summary>
        /// Recurse through the filereferences to get all the references required for an activity Dll.
        /// </summary>
        /// <param name="activityFile">The activity file.</param>
        /// <param name="fileReferences">The file references.</param>
        /// <param name="associationList">The complete list of references.</param>
        public static void GetAllAssociatedFiles(FileReference activityFile, Collection<FileReference> fileReferences, Collection<FileReference> associationList)
        {
            foreach (string filePath in activityFile.references)
            {
                FileReference reference = FileReference.GetFileReference(fileReferences, filePath);

                // Will happen if the reference is loaded from appdomain.
                if (reference != null)
                {
                    if (!associationList.Any(f => f.Checksum != null && f.Checksum.Equals(reference.Checksum)))
                    {
                        associationList.Add(reference);
                        GetAllAssociatedFiles(reference, fileReferences, associationList);
                    }
                }
            }
        }

        /// <summary>
        /// Overriding to string for automation Id purposes.
        /// </summary>
        /// <returns>The string representation of the object.</returns>
        public override string ToString()
        {
            if (!string.IsNullOrEmpty(this.displayName))
            {
                return this.displayName;
            }
            return base.ToString();
        }
        #endregion

        #region INotifyPropertyChanged Members
        [field:NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string name)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(name));
        }

        #endregion
    }
}